home *** CD-ROM | disk | FTP | other *** search
/ The Best of MacTutor - S…e Code for Volumes 1 to 5 / The Best of MacTutor - Source Code for Volume 1-5 (Wayzata Technology)(6031)(1990).bin / Source Code / #39 (Dec 88) / KeyEdDemo / KeyEdDemo.Pas < prev    next >
Pascal/Delphi Source File  |  1988-06-26  |  15KB  |  584 lines

  1. PROGRAM KeyEdDemo;
  2. { copyright F.Samuel and MacTutor 1988 }
  3. { use only with system 4.x or later }
  4. {$I-}
  5.  { turn off automatic initialization }
  6. {$L keyEdDemoRes}
  7.  { load resource file }
  8.  
  9.     CONST
  10.         DialogID = 128;
  11.         EditCodeItem = 2;
  12.         LoadBtn = 5;
  13.         UserItem = 6;
  14.         AsciiCharItem = 7;
  15.         KeyCodeItem = 8;
  16.         AboutAlrt = 129;
  17.         LoadAlrt = 130;
  18.         AppleID = 1;
  19.         AboutItem = 1;
  20.         FileID = 2;
  21.         EditID = 3;
  22.         CutItem = 3;
  23.         CopyItem = 4;
  24.         PasteItem = 5;
  25.         ClearItem = 6;
  26.  
  27. { Low-memory globals }
  28.         KybdType = $21E;
  29.         ScsiFlag = $B22;
  30.         Key1Trans = $29E;
  31.         BasicGlob = $2B6;
  32.  
  33. { KCaps ID of various keyboards }
  34.         MacPlusKbd = 11;
  35.         MacClassicKbd = 3;
  36.         EuroMacKbd = 259;
  37.         ADBKbd = 1;
  38.         ADBExtKbd = 2;
  39.         ADBIsoKbd = 4;
  40.  
  41.     TYPE
  42.         Prect = ^Rect; { for type-casting a pointer }
  43.         PLong = ^LongInt;
  44.         PWord = ^integer;
  45.         PPoint = ^Point;
  46.  
  47.     VAR
  48.         Finished, EditOn : Boolean;
  49.         DragRect : Rect;
  50.         MouseLocal : Point;
  51.         KCapsHandle, KChrHandle : Handle;
  52.         DemoDialog : DialogPtr;
  53.         HiliteKeys : SET OF 0..127;
  54.         EditKey, EditModifs : Integer;
  55.  
  56. { utilities to acces properties of items in a dialog }
  57.  
  58.     PROCEDURE SetDItemText (TheDialog : DialogPtr;
  59.                                     TheItem : Integer;
  60.                                     TheText : Str255);
  61.         VAR
  62.             ItemType : integer;     { should be a text item }
  63.             ItemHandle : Handle;
  64.             DispRect : Rect;
  65.     BEGIN
  66.         GetDItem(TheDialog, TheItem, ItemType, ItemHandle, DispRect);
  67.         SetIText(ItemHandle, TheText)
  68.     END;
  69.  
  70.     FUNCTION GetDItemText (TheDialog : DialogPtr;
  71.                                     TheItem : Integer) : Str255;
  72.         VAR
  73.             ItemType : integer;    { should be a text item }
  74.             ItemHandle : Handle;
  75.             DispRect : Rect;
  76.             TheText : Str255;
  77.     BEGIN
  78.         GetDItem(TheDialog, TheItem, ItemType, ItemHandle, DispRect);
  79.         GetIText(ItemHandle, TheText);
  80.         GetDItemText := TheText
  81.     END;
  82.  
  83.     FUNCTION GetDItemRect (TheDialog : DialogPtr;
  84.                                     TheItem : Integer) : Rect;
  85.         VAR
  86.             ItemType : integer;
  87.             ItemHandle : Handle;
  88.             DispRect : Rect;
  89.     BEGIN
  90.         GetDItem(TheDialog, TheItem, ItemType, ItemHandle, DispRect);
  91.         GetDItemRect := DispRect
  92.     END;
  93.  
  94.     PROCEDURE SetUserProc (TheDialog : DialogPtr;
  95.                                     TheItem : Integer;
  96.                                     TheProc : procPtr);
  97.         VAR
  98.             ItemType : integer;     { should be an UserItem ! }
  99.             ItemHandle : Handle;
  100.             DispRect : Rect;
  101.     BEGIN
  102.         GetDItem(TheDialog, TheItem, ItemType, ItemHandle, DispRect);
  103.         SetDItem(TheDialog, TheItem, ItemType, Handle(theProc), DispRect)
  104.     END;
  105.  
  106. { function NumToString , more convenient that way ... }
  107.  
  108.     FUNCTION FNumToString (TheNum : LongInt) : Str255;
  109.         VAR
  110.             TheString : Str255;
  111.     BEGIN
  112.         NumToString(TheNum, TheString);
  113.         FNumToString := TheString
  114.     END;
  115.  
  116. { interface for external procedures and functions : }
  117.  
  118.     FUNCTION KeyTrans (transData : Ptr;
  119.                                     keycode : INTEGER;
  120.                                     VAR state : LONGINT) : LONGINT;
  121.     INLINE
  122.         $A9C3;
  123.  
  124.     PROCEDURE poke (address : longint;
  125.                                     value : integer);
  126.     external; { puts low byte of value at address }
  127.  
  128.     FUNCTION peek (address : longint) : integer;
  129.     external; { returns byte at address }
  130.  
  131.     FUNCTION Key12Trans (KeyCode, KeyModifs : Integer) : Integer;
  132.     external;
  133.  
  134. { Pascal procedures and functions follow ... }
  135.  
  136.     FUNCTION GetAscii (KeyCode, Modifs : integer;
  137.                                     VAR Ascii : integer) : Boolean;
  138.     { Returns true if it's a normal key , Ascii returns ascii code even if it's a double strike}
  139.         VAR
  140.             State : LongInt;
  141.     BEGIN
  142.         State := 0;
  143.         Ascii := LoWord(KeyTrans(KChrHandle^, KeyCode + Modifs, State));
  144.         IF Ascii = 0 THEN
  145.             BEGIN
  146.                 GetAscii := false;
  147.                 Ascii := LoWord(KeyTrans(KChrHandle^, KeyCode + Modifs, State))
  148.             END
  149.         ELSE
  150.             GetAscii := true
  151.     END;
  152.  
  153.     FUNCTION GetKbd : Integer;
  154.     { returns KCAP ID of current keyboard }
  155.         VAR
  156.             TempID : integer;
  157.             addr : Plong;
  158.             SCSIMac : boolean;
  159.     BEGIN
  160.         TempId := peek(KybdType);
  161.         SCSIMac := BitTst(Ptr(SCSIFlag), 5);
  162.         IF (NOT SCSIMac) AND (TempID <> MacPlusKbd) THEN
  163.             BEGIN
  164.                 tempId := MacClassicKbd;
  165.                 addr := Plong(Key1Trans);
  166.                 IF peek(addr^ + 10) <> 0 THEN { test itlc byte }
  167.                     TempId := EuroMacKbd
  168.             END;
  169.         GetKbd := TempID
  170.     END;
  171.  
  172.     PROCEDURE SetUpMenus;
  173.         VAR
  174.             ID : integer;
  175.     BEGIN
  176.         FOR ID := AppleID TO EditID DO
  177.             InsertMenu(GetMenu(ID), 0);
  178.         AddResMenu(GetMHandle(AppleID), 'DRVR');
  179.         DrawMenuBar
  180.     END;
  181.  
  182.     PROCEDURE MainCaps (PROCEDURE treatIt (rgn : RgnHandle;
  183.                                                                     Code : integer));
  184.         VAR
  185.             Hrgn : RgnHandle;
  186.             addr : PWord;
  187.             Paddr : PPoint;
  188.             NumRgn, NumRect, NumKeys, i, j : integer;
  189.             Keycode, dh, dv : Integer;
  190.             TL, BR : point;
  191.             KRect : rect;
  192.     BEGIN
  193.         BEGIN
  194.             SetPort(DemoDialog);
  195.             GetMouse(MouseLocal);
  196.             ClipRect(DemoDialog^.PortRect);
  197.             Hlock(KcapsHandle);
  198.             Addr := Pword(Ord4(KcapsHandle^) + 16);
  199.             NumRgn := Addr^;
  200.             IF NumRgn > 0 THEN
  201.                 FOR i := 1 TO NumRgn DO
  202.                     BEGIN
  203.                         Addr := Pword(Ord4(Addr) + 2);
  204.                         NumRect := addr^;
  205.                         Hrgn := NewRgn;
  206.                         OpenRgn;
  207.                         SetPt(TL, 0, 0);
  208.                         Addr := Pword(Ord4(Addr) + 2);
  209.                         FOR j := 0 TO NumRect DO
  210.                             BEGIN
  211.                                 PAddr := PPoint(addr);
  212.                                 BR := Paddr^;
  213.                                 Pt2Rect(TL, BR, Krect);
  214.                                 FrameRect(Krect);
  215.                                 TL := BR;
  216.                                 Addr := Pword(Ord4(Addr) + 4);
  217.                             END;
  218.                         CloseRgn(Hrgn);
  219.                         NumKeys := addr^;
  220.                         FOR j := 0 TO NumKeys DO
  221.                             BEGIN
  222.                                 Addr := Pword(Ord4(Addr) + 2);
  223.                                 KeyCode := addr^;
  224.                                 Addr := Pword(Ord4(Addr) + 2);
  225.                                 dv := addr^;
  226.                                 Addr := Pword(Ord4(Addr) + 2);
  227.                                 dh := addr^;
  228.                                 OffsetRgn(Hrgn, dh, dv);
  229.                                 TreatIt(Hrgn, Keycode MOD 128);
  230.                             END;
  231.                         DisposeRgn(Hrgn);
  232.                     END;
  233.             Hunlock(KcapsHandle)
  234.         END
  235.     END;
  236.  
  237.     PROCEDURE InvertKey (Rgn : RgnHandle);
  238.         VAR
  239.             InnerRgn : RgnHandle;
  240.     BEGIN
  241.         InnerRgn := NewRgn;
  242.         CopyRgn(Rgn, InnerRgn);
  243.         InsetRgn(InnerRgn, 2, 2);
  244.         InvertRgn(InnerRgn);
  245.         DisposeRgn(InnerRgn)
  246.     END;
  247.  
  248.     PROCEDURE DrawKey (rgn : RgnHandle;
  249.                                     Code : integer);
  250.         VAR
  251.             DrawRgn : RgnHandle;
  252.             AsciiCode : Integer;
  253.             NormalKey : boolean;
  254.     BEGIN
  255.         FrameRgn(Rgn);
  256.         DrawRgn := NewRgn;
  257.         CopyRgn(Rgn, DrawRgn);
  258.         InsetRgn(DrawRgn, 1, 1);
  259.         SetClip(DrawRgn);
  260.         EraseRgn(DrawRgn);
  261.         NormalKey := GetAscii(Code, EditModifs, AsciiCode);
  262.         WITH DrawRgn^^.rgnBBox DO
  263.             MoveTo(left + 1, bottom - 2);
  264.         DrawChar(Chr(AsciiCode));
  265.         DisposeRgn(DrawRgn);
  266.         IF Code IN HiliteKeys THEN
  267.             InvertKey(Rgn);
  268.         ClipRect(DemoDialog^.PortRect)
  269.     END;
  270.  
  271.     PROCEDURE UserDraw (TheWindow : WindowPtr;
  272.                                     ItemNum : Integer);
  273.         VAR
  274.             FillPat : Pattern;
  275.             TheRect : Rect;
  276.     BEGIN
  277.         TheRect := GetDItemRect(DemoDialog, ItemNum);
  278.         GetIndPattern(FillPat, 0, 10);
  279.         FillRect(TheRect, FillPat);
  280.         FrameRect(TheRect);
  281.         MainCaps(DrawKey)
  282.     END;
  283.  
  284.     PROCEDURE InitThings;
  285.         VAR
  286.             i, Error : Integer;
  287.     BEGIN { Get KybdID and KCaps ; SetUserProc;Show dialog }
  288.         FlushEvents(EveryEvent, 0);
  289.         InitGraf(@ThePort);
  290.         InitFonts;
  291.         InitWindows;
  292.         InitMenus;
  293.         TEInit;
  294.         InitDialogs(NIL);
  295.         InitCursor;
  296.         SetEventMask(EveryEvent - KeyUpMask);
  297.         MaxApplZone;
  298.         FOR i := 1 TO 5 DO
  299.             MoreMasters;
  300.         WITH ScreenBits.Bounds DO
  301.             SetRect(DragRect, left + 4, top + 24, right - 4, bottom - 4);
  302.         SetUpMenus;
  303.         KCapsHandle := GetResource('KCAP', GetKbd);
  304.         DetachResource(KCapsHandle); { don't let a DA release it on your back ! }
  305.         KChrHandle := GetResource('KCHR', 0);
  306.         Error := HandToHand(KChrHandle); { make a copy , so we can modify it }
  307.         MoveHHi(KChrHandle); { keep heap unfragmented }
  308.         HLock(KChrHandle);
  309.         HiliteKeys := []; { no keys to hilite until the user selects one }
  310.         EditModifs := 0;
  311.         EditOn := False; { wait for the user to select a key to edit }
  312.         DemoDialog := GetNewDialog(DialogID, NIL, WindowPtr(-1));
  313.         SetUserProc(DemoDialog, UserItem, @UserDraw);
  314.         ShowWindow(DemoDialog);
  315.         Finished := False
  316.     END;
  317.  
  318.     PROCEDURE DoMenu (Code : longint);
  319.         VAR
  320.             MenuNum, ItemNum, Temp : integer;
  321.             DeskAccName : str255;
  322.     BEGIN
  323.         IF code <> 0 THEN
  324.             BEGIN
  325.                 MenuNum := HiWord(Code);
  326.                 ItemNum := LoWord(Code);
  327.                 CASE MenuNum OF
  328.                     AppleID : 
  329.                         IF ItemNum = AboutItem THEN
  330.                             Temp := Alert(AboutAlrt, NIL)
  331.                         ELSE
  332.                             BEGIN
  333.                                 GetItem(GetMHandle(AppleID), ItemNum, DeskAccName);
  334.                                 Temp := OpenDeskAcc(DeskAccName);
  335.                             END;
  336.                     FileID : 
  337.                         Finished := True;
  338.                     EditID : 
  339.                         IF NOT SystemEdit(ItemNum - 1) THEN
  340.                             IF (FrontWindow = DemoDialog) AND EditOn THEN
  341.                                 CASE ItemNum OF
  342.                                     CutItem : 
  343.                                         DlgCut(DemoDialog);
  344.                                     CopyItem : 
  345.                                         DlgCopy(DemoDialog);
  346.                                     PasteItem : 
  347.                                         DlgPaste(DemoDialog);
  348.                                     ClearItem : 
  349.                                         DlgDelete(DemoDialog);
  350.                                     OTHERWISE
  351.                                 END;
  352.                     OTHERWISE
  353.                 END;
  354.                 HiliteMenu(0)
  355.             END
  356.     END;
  357.  
  358.     PROCEDURE StartEdit (KeyCode, AsciiCode : Integer);
  359.     { enable editing  the ascii code of the selected key }
  360.     BEGIN
  361.         EditKey := KeyCode;
  362.         HiliteKeys := HiliteKeys + [EditKey];
  363.         SetDItemText(DemoDialog, EditCodeItem, FNumToString(AsciiCode));
  364.         SelIText(DemoDialog, EditCodeItem, 0, MaxInt);
  365.         SetDItemText(DemoDialog, KeyCodeItem, FNumToString(EditKey));
  366.         SetDItemText(DemoDialog, AsciiCharItem, Chr(AsciiCode));
  367.         EditOn := true
  368.     END;
  369.  
  370.     PROCEDURE DrawEditKRgn (Rgn : RgnHandle;
  371.                                     TheCode : Integer);
  372.     { update key contents in case it changed }
  373.     BEGIN
  374.         IF TheCode = EditKey THEN
  375.             DrawKey(Rgn, EditKey)
  376.     END;
  377.  
  378.     PROCEDURE ValidateEdit;
  379.     { validate user editing of the selected key }
  380.         VAR
  381.             BlockNumber : Integer;
  382.             MapAddress, NewAsciiCode : LongInt;
  383.     BEGIN
  384.         IF EditOn THEN
  385.             BEGIN
  386.                 StringToNum(GetDItemText(DemoDialog, EditCodeItem), NewAsciiCode);
  387.                 MapAddress := Ord4(KChrHandle^);
  388.                 BlockNumber := Peek(MapAddress + BitShift(EditModifs, -8) + 2);
  389.                 Poke(MapAddress + 260 + BlockNumber * 128 + EditKey, NewAsciiCode);
  390.                 EditOn := False;
  391.                 SetDItemText(DemoDialog, EditCodeItem, '');
  392.                 SetDItemText(DemoDialog, KeyCodeItem, '');
  393.                 SetDItemText(DemoDialog, AsciiCharItem, '');
  394.                 HiliteKeys := HiliteKeys - [EditKey];
  395.                 MainCaps(DrawEditKRgn);
  396.             END
  397.     END;
  398.  
  399.     PROCEDURE CodeXORModifs (ModifCode : Integer;
  400.                                     VAR Modifs : Integer);
  401.     { XOR new modifier with the already selected ones }
  402.         VAR
  403.             TempModifs : Integer;
  404.     BEGIN
  405.         TempModifs := 0;
  406.         BitSet(@TempModifs, $3E - ModifCode);
  407.         Modifs := BitXor(Modifs, TempModifs)
  408.     END;
  409.  
  410.     FUNCTION EditPerm (KeyCode, Modifs : Integer;
  411.                                     VAR AsciiCode : Integer) : Boolean;
  412. { can that key  be edited ? }
  413.     BEGIN
  414.         IF GetAscii(KeyCode, Modifs, AsciiCode) THEN
  415.             EditPerm := true
  416.         ELSE { check that it's not a double strike nor a modifier }
  417.             EditPerm := (AsciiCode = 0) AND NOT (KeyCode IN [$3C..$3E])
  418.     END;
  419.  
  420.     PROCEDURE ActiveClick (rgn : RgnHandle;
  421.                                     Code : integer);
  422.         VAR
  423.             AsciiCode : Integer;
  424.     BEGIN
  425.         IF PtInRgn(MouseLocal, Rgn) THEN
  426.             IF (Code <> EditKey) OR NOT EditOn THEN
  427.                 BEGIN
  428.                     ValidateEdit;
  429.                     IF Code IN [$37..$3B] THEN { modifier key was clicked }
  430.                         BEGIN
  431.                             CodeXORModifs(Code, EditModifs);
  432.                             IF Code IN HiliteKeys THEN
  433.                                 HiliteKeys := HiliteKeys - [Code]
  434.                             ELSE
  435.                                 HiliteKeys := HiliteKeys + [Code];
  436.                             IF EditPerm(EditKey, EditModifs, AsciiCode) THEN
  437.                                 StartEdit(EditKey, AsciiCode);
  438.                             MainCaps(DrawKey)  { redraw the keyboard to update hiliting }
  439.                         END
  440.                     ELSE IF EditPerm(Code, EditModifs, AsciiCode) THEN
  441.                         BEGIN
  442.                             InvertKey(Rgn);    { hilite it , and let user edit it }
  443.                             StartEdit(Code, AsciiCode)
  444.                         END
  445.                 END
  446.     END;
  447.  
  448.     FUNCTION CheckSysTrans (TransData : Ptr) : Boolean;
  449.     { is TransData really a pointer to the system mapping table ?? }
  450.         VAR
  451.             KeyCode, AsciiCode : Integer;
  452.     BEGIN
  453.         CheckSysTrans := true;
  454.         FOR KeyCode := 0 TO 16 DO
  455.             BEGIN
  456.                 AsciiCode := Key12Trans(KeyCode, 0); { let system compute it }
  457.                 IF Peek(Ord4(TransData) + 260 + KeyCode) <> AsciiCode THEN
  458.                     CheckSysTrans := False; { compare with our table }
  459.                 IF AsciiCode = 0 THEN
  460.                     BEGIN
  461.                         AsciiCode := Key12Trans(KeyCode, 0);
  462.                         FlushEvents(EveryEvent, 0) { double strike may have posted an event , flush it }
  463.                     END
  464.             END
  465.     END;
  466.  
  467.     FUNCTION GetSysTrans (VAR TheTrans : Ptr) : boolean;
  468.         VAR
  469.             BGlob, Addr : PLong;
  470.     BEGIN
  471.         Bglob := PLong(BasicGlob);
  472.         Addr := PLong(BGlob^ + 14);
  473.     { not documented , so better check if we can rely on it ! }
  474.         TheTrans := Ptr(Addr^);
  475.         GetSysTrans := CheckSysTrans(TheTrans)
  476.     END;
  477.  
  478.     PROCEDURE DoLoad;
  479.         VAR
  480.             TheSize : LongInt;
  481.             RamTransPtr : Ptr;
  482.             SysKchr : Handle;
  483.             AppResFile : integer;
  484.     BEGIN
  485.         IF Alert(LoadAlrt, NIL) = Ok THEN
  486.             BEGIN
  487.                 TheSize := GetHandleSize(KChrHandle);
  488.                 IF GetSysTrans(RamTransPtr) THEN { see above ... }
  489.                     BlockMove(KChrHandle^, RamTransPtr, TheSize);
  490.                 AppResFile := CurResFile;
  491.                 UseResFile(0);
  492.                 SysKchr := GetResource('KCHR', 0);
  493.                 BlockMove(KChrHandle^, SysKChr^, TheSize);
  494.                 ChangedResource(SysKChr);
  495.                 UpdateResFile(0);
  496.                 UseResFile(AppResFile)
  497.             END
  498.     END;
  499.  
  500.     PROCEDURE DoDialog (TheEvent : EventRecord);
  501.         CONST
  502.             Enter = $03;
  503.             Return = $0D;
  504.         VAR
  505.             TheDialog : DialogPtr;
  506.             ItemHit, CharCode : Integer;
  507.             PassIt : Boolean;
  508.     BEGIN
  509.         WITH TheEvent DO
  510.             IF What = KeyDown THEN
  511.                 BEGIN { filter key down events }
  512.                     CharCode := BitAnd(Message, CharCodeMask);
  513.                     PassIt := False;
  514.                     IF BitAnd(Modifiers, CmdKey) <> 0 THEN
  515.                         DoMenu(MenuKey(Chr(CharCode)))
  516.                     ELSE IF EditOn THEN
  517.                         IF CharCode IN [Return, Enter] THEN
  518.                             ValidateEdit
  519.                         ELSE
  520.                             PassIt := True
  521.                 END
  522.             ELSE
  523.                 PassIt := True;
  524.         IF PassIt THEN
  525.             IF DialogSelect(TheEvent, TheDialog, ItemHit) THEN
  526.                 CASE ItemHit OF
  527.                     UserItem : 
  528.                         MainCaps(ActiveClick);
  529.                     LoadBtn : 
  530.                         DoLoad;
  531.                     OTHERWISE
  532.                 END
  533.     END;
  534.  
  535.     PROCEDURE MainLoop;
  536.         VAR
  537.             GotEvent : Boolean;
  538.             TheEvent : EventRecord;
  539.             TheWindow : WindowPtr;
  540.     BEGIN
  541.         SystemTask;
  542.         GotEvent := GetNextEvent(EveryEvent, TheEvent);
  543.         IF IsDialogEvent(TheEvent) THEN
  544.             DoDialog(TheEvent)
  545.         ELSE IF GotEvent THEN
  546.             WITH TheEvent DO
  547.                 CASE What OF
  548.                     MouseDown : 
  549.                         CASE FindWindow(Where, TheWindow) OF
  550.                             inMenuBar : 
  551.                                 DoMenu(MenuSelect(Where));
  552.                             inSysWindow : 
  553.                                 SystemClick(TheEvent, TheWindow);
  554.                             inContent : 
  555.                                 IF TheWindow <> FrontWindow THEN
  556.                                     SelectWindow(TheWindow);
  557.                             inDrag : 
  558.                                 DragWindow(TheWindow, Where, DragRect);
  559.                             inGoaway : 
  560.                                 IF TheWindow <> FrontWindow THEN
  561.                                     SelectWindow(TheWindow)
  562.                                 ELSE IF TrackGoAway(TheWindow, Where) THEN
  563.                                     Finished := True;
  564.                             OTHERWISE
  565.                         END;
  566.                     KeyDown : 
  567.                         IF BitAnd(Modifiers, CmdKey) <> 0 THEN
  568.                             DoMenu(MenuKey(Chr(BitAnd(Message, CharCodeMask))));
  569.                     UpdateEvt : 
  570.                         BEGIN { just in case ... }
  571.                             TheWindow := WindowPtr(Message);
  572.                             BeginUpdate(TheWindow);
  573.                             EndUpdate(TheWindow)
  574.                         END;
  575.                     OTHERWISE
  576.                 END
  577.     END;
  578.  
  579. BEGIN
  580.     InitThings;
  581.     REPEAT
  582.         MainLoop
  583.     UNTIL Finished
  584. END.